//
//  BoundaryHandler.h
//  NSESolver
//
//  Created by Patrick Zulian on 3/29/11.
//  Copyright 2011 ICS - USI - Lugano. All rights reserved.
//

#ifndef NSESolver_BoundaryHandler_H
#define NSESolver_BoundaryHandler_H

#include <map.h>
#include "Types.h"
#include "NSEUtils.h"

#include "CartesianTopology.h"
#include "Communicator.h"
#include "BufferSpace.h"
#include "Buffer.h"
#include "Reference.h"


#include "Obstacle.h"

namespace nsesolver {
    
    class CFDProblem;  
    
    /// default boundary condition type
    enum BoundaryConditionType {
        NoSlip,
        Slip,
        MovingBelt
    };
    
    /**
     \brief an enum for the id of the send buffer
     */
    enum BufferId {
        UNorth = 0,
        USouth,
        UWest,
        UEast,
        
        VNorth,
        VSouth,
        VWest,
        VEast,
        
        Recv,
        
        PNorth = UNorth,
        PSouth = USouth,
        PWest = UWest,
        PEast = UEast
        
        
    };
    
    /**
     \brief definition of the function pointer for the boundaries
     */
    typedef double (*boundaryValGetter)(double);
    
    class BoundaryHandler {
    private:
        //some typedes
        typedef mpi::CartesianTopology CartesianTopology;
        typedef mpi::Communicator Communicator;
        
        //dictionaries containing the getters with respect to the caridinal direction
        std::map<CardinalDirections, boundaryValGetter> uBoundary, vBoundary;
        
        //mpi cartesian topology
        CartesianTopology cart;
        
        //buffers for sending data
        Reference< BufferSpace< double > > bufferSpace;
        
        Obstacle obstacle;
    public:
        /**
         \brief constructor
         \param the current cartesian topology
         */
        BoundaryHandler(const CartesianTopology &cart, const Obstacle &obstacle);
        ~BoundaryHandler();
        
        /**
         \brief function to set all the boudaries of u,v; it manage also to send the right ghost array to the other processors
         */
        void setBoundary(CFDProblem &problem);
        /**
         \brief function to set all the boudaries of p; it manage also to send the right ghost array to the other processors
         */
        void setPBoundary(CFDProblem &problem);
        
        void setFGBoundary(Matrixd &F, Matrixd &G, const CFDProblem & problem);
        
        
        /**
         \brief set the boundary of u
         \param the direction of the boudary
         \param the boundary type
         */
        void setUBoundaryType(const CardinalDirections dir, const BoundaryConditionType type);
        /**
         \brief set the boundary of v
         \param the direction of the boudary
         \param the boundary type
         */
        void setVBoundaryType(const CardinalDirections dir, const BoundaryConditionType type);
        
        /**
         \brief set the boundary of u
         \param the direction of the boudary
         \param the function pointer to set the boundary
         */
        void setUBoundaryType(const CardinalDirections dir, const boundaryValGetter getter);
        /**
         \brief set the boundary of u
         \param the direction of the boudary
         \param the function pointer to set the boundary
         */
        void setVBoundaryType(const CardinalDirections dir, const boundaryValGetter getter);
        
        
        /**
         \brief set u with inflow and outflow
         \param the function pointer to set the amount of inflow/outflow
         */
        void setUInflowOutflow(boundaryValGetter inflow);
        /**
         \brief set v with inflow and outflow
         \param the function pointer to set the amount of inflow/outflow
         */
        void setVInflowOutflow(boundaryValGetter inflow);
        
        
        inline
        const Obstacle & getObstacle() const 
        {
            return  obstacle;
        }
        
    private:
        /**
         \brief send uv to the other processors
         \param direction to send
         \param range of u data to send
         \param range of v data to send
         */
        void uvSendRecv(CardinalDirections cardDir, const MatrixHandle< Matrixd > & uh, const MatrixHandle< Matrixd > & vh);
        /**
         \brief send p to the other processors
         \param direction to send
         \param range of p data to send
         */
        void pSendRecv(CardinalDirections cardDir, const MatrixHandle< Matrixd > & ph);
        
        /**
         \brief update uv with the data received from other processor
         \param matrix u
         \param matrix v
         \param imax
         \param jmax
         */
        void uvUpdate(Matrixd &u, Matrixd &v, const int imax, const int jmax );
        /**
         \brief update p with the data received from other processor
         \param matrix p
         \param imax
         \param jmax
         */
        void pUpdate(Matrixd &p, const int imax, const int jmax);
        
        
        /**
         \brief pack the data in the buffer for sending
         \param the buffer where to store the data
         \param the handler to write
         */
        static inline void packData( Buffer< double > &buffer, const MatrixHandle< Matrixd > &mh )
        {
            NSE_SOLVER_ASSERT(buffer.size()>=mh.size());
            
            int index = 0;
            
            for (int c = 0; c < mh.ymax(); c++) {
                for (int r = 0; r < mh.xmax(); r++) {
                    buffer[index++] = mh(r, c);
                }
            }
        }
        
        /**
         \brief unpack the data received
         \param the buffer containing the data
         \param the handler where to write
         */
        static inline void unpackData( Buffer< double > &buffer, const MatrixHandle< Matrixd > &mh )
        {
            NSE_SOLVER_ASSERT(buffer.size()>=mh.size());
            
            int index = 0;
            
            
            for (int c = 0; c < mh.ymax(); c++) {
                for (int r = 0; r < mh.xmax(); r++) {
                    mh(r, c) = buffer[index++];
                }
            }
        }
        
        /// default function pointer for left/right no slip boundary
        static inline double getLeftRightValueNoSlip(const double neighbour)
        {
            return -neighbour;
        }
        
        /// default function pointer for left/right slip boundary
        static inline double getLeftRightValueSlip(double neighbour)
        {
            return neighbour;
        }
        
        /// default function pointer for the mooving belt
        static inline double getLeftRightValueBelt(const double neighbour)
        {
            return 2.0-neighbour;
        }
        
        /// default function pointer for up/down boundary
        static inline double getOrthogonalValue(const double neighbour)
        {
            return 0;
        }
        
        /// default function pointer for outflow
        static inline double getOutflowValue(const double neighbour)
        {
            return neighbour;
        }
    };
    
}
#endif //NSESolver_BoundaryHandler_H
